﻿using System;
using System.Collections.Generic;
using System.Linq;
using Sustainalytics.ClientDW.Entities;
using Sustainalytics.Entities;
using Sustainalytics.Portfolios.BL.Model;
using Sustainalytics.Utils;

namespace Sustainalytics.Portfolios.BL
{
    public interface IPortfolioOutput
    {
        void Write(Portfolio portfolio, ICollection<PortfolioSecuritiesRow> securities,
            PortfolioUserClaims userClaim);
    }

    public class PortfolioSecuritiesRow
    {
        public bool IsValid { get; private set; }

        public PortfolioSecurityModel Security { get; private set; }

        public PortfolioSecuritiesRow(PortfolioSecurityModel portfolioMatches, string matchingDetails, bool isValid)
        {
            Security = portfolioMatches;
            MatchingDetails = matchingDetails;
            IsValid = isValid;
        }

        public string MatchingDetails { get; private set; }
    }


    internal class MatchingResult
    {
        private readonly Tuple<IList<Company>, string> _result;

        public bool IsValid { get; private set; }

        public PortfolioMatchType MatchType { get; set; }

        public static MatchingResult Null
        {
            get { return new MatchingResult(null, ""); }
        }

        public MatchingResult(IList<Company> matches, string matchDetails)
        {
            _result = new Tuple<IList<Company>, string>(matches, matchDetails);
            IsValid = !(
                (matches == null && matchDetails.IsNullOrEmpty()) ||            //When MatchingResult.Null is used 
                (!matches.Any() && matchDetails.IsNullOrEmpty()) ||             //CompanyHasNoSecurity
                (matchDetails != null && !matchDetails.IsNullOrEmpty())         //SecuritiesMatchDifferentCompanies
                );
        }

        public MatchingResult(Tuple<IList<Company>, string> result)
        {
            _result = result;
        }

        public IList<Company> Matches
        {
            get { return _result.Item1; }
        }

        public string MatchDetails { get { return _result.Item2; } }

    }

    internal abstract class MatchingSecuritiesAlgorithmBase
    {

        protected List<IRule<Company>> RulesEngine;
        public MatchingResult GetSecurity(ICollection<Company> companySecurities)
        {
            var matchingRules = RulesEngine.Where(rule => rule.Passes(companySecurities)).ToList();

            if (matchingRules.Count != 1)
            {
                return MatchingResult.Null;
            }

            return new MatchingResult
                (
                matchingRules.First().PassingResolution.Invoke(companySecurities),
                matchingRules.First().ErrorResolution.Invoke(companySecurities)
                )
            {
                MatchType = matchingRules.First().MatchType.Invoke()
            };
        }
    }

    internal class MatchingSecuritiesAlgorithm : MatchingSecuritiesAlgorithmBase
    {
        public MatchingSecuritiesAlgorithm()
        {
            RulesEngine =
             new List<IRule<Company>>
                 {
                    new ExactMatch()
                    ,
                    new SecuritiesMatchSameCompany()
                    ,
                    new SecuritiesMatchDifferentCompanies()
                    ,
                    new CompanyHasNoSecurity()
                 };
        }
    }

    internal class MatchingCompanySecuritiesAlgorithm : MatchingSecuritiesAlgorithmBase
    {
        public MatchingCompanySecuritiesAlgorithm()
        {
            RulesEngine =
              new List<IRule<Company>>
                {
                    new ExactMatch()    
                    ,
                    new SecuritiesMatchSameCompany()
                    ,
                    new CompanyHasNoSecurity()
                };
        }
    }

    internal interface IRule<T>
    {
        Func<ICollection<T>, string> ErrorResolution { get; }

        Func<PortfolioMatchType> MatchType { get; }

        bool Passes(ICollection<T> collection);

        Func<ICollection<T>, IList<T>> PassingResolution { get; }
    }

    internal abstract class Rule<T> : IRule<T>
    {
        public Func<ICollection<T>, string> ErrorResolution { get; protected set; }

        public Func<PortfolioMatchType> MatchType { get; protected set; }

        protected Predicate<ICollection<T>> ValidationPredicate { get; set; }

        public bool Passes(ICollection<T> collection)
        {
            return ValidationPredicate(collection);
        }

        public Func<ICollection<T>, IList<T>> PassingResolution { get; protected set; }
    }

    internal class SecuritiesMatchSameCompany : Rule<Company>
    {
        public SecuritiesMatchSameCompany()
        {
            MatchType = () => PortfolioMatchType.SecuritiesMatchSameCompany;

            ErrorResolution = d => string.Empty;

            ValidationPredicate = d =>
            {

                var result = d.Count > 1
                    && d.GroupBy(x => x.CompanyId).Count() == 1;

                return result;
            };

            PassingResolution = d =>
            {
                var mainSecurity = d.FirstOrDefault();
                return new List<Company>() { mainSecurity };
            };
        }
    }

    internal class CompanyLevelSecurity : Rule<Company>
    {
        public CompanyLevelSecurity()
        {
            MatchType = () => PortfolioMatchType.CompanyLevelSecurity;

            ErrorResolution = d => string.Empty;

            PassingResolution = d => new List<Company> { d.First() };
        }
    }

    internal class CompanyHasNoSecurity : Rule<Company>
    {
        public CompanyHasNoSecurity()
        {
            MatchType = () => PortfolioMatchType.CompanyHasNoSecurity;

            ErrorResolution = d => string.Empty;

            ValidationPredicate = d =>
            {
                var result = (!d.GroupBy(x => x.CompanyId).Any());

                return result;
            };
            PassingResolution = d => new List<Company> { };
        }
    }

    internal class SecuritiesMatchDifferentCompanies : Rule<Company>
    {
        public SecuritiesMatchDifferentCompanies()
        {
            MatchType = () => PortfolioMatchType.SecuritiesMatchDifferentCompanies;

            ErrorResolution = GetErrorMessages;

            ValidationPredicate = details =>
            {
                var result = (details.GroupBy(x => x.CompanyId).Count() > 1);

                return result;
            };
            PassingResolution = details => details.GroupBy(x => x.CompanyId).Select(x => x.First()).ToList();
        }

        private static string GetErrorMessages(ICollection<Company> details)
        {
            if (details == null)
            {
                throw new ArgumentNullException("details");
            }
            var result =
                details.GroupBy(x => x.CompanyId, x => x.CompanyName)
                    .Select(itemInGroup => itemInGroup.FirstOrDefault())
                    .Where(companyName => companyName != null)
                    .ToList();
            return string.Join(", ", result);
        }
    }

    internal class ExactMatch : Rule<Company>
    {
        public ExactMatch()
        {
            MatchType = () => PortfolioMatchType.ExactMatch;

            ErrorResolution = d => string.Empty;

            ValidationPredicate = d =>
            {
                var result = d.Count == 1;
                return result;
            };
            PassingResolution = d => new List<Company>() { d.First() };
        }
    }

    internal interface ISpecification<T>
    {
        bool IsSatisfiedBy(T candidate);
        ISpecification<T> And(ISpecification<T> other);
    }

    internal class AndSpecification<T> : CompositeSpecification<T>
    {

        private readonly ISpecification<T> _left;
        private readonly ISpecification<T> _right;

        public AndSpecification(ISpecification<T> left, ISpecification<T> right)
        {
            _left = left;
            _right = right;
        }

        public override bool IsSatisfiedBy(T candidate)
        {
            return _left.IsSatisfiedBy(candidate) && _right.IsSatisfiedBy(candidate);
        }
    }

    internal abstract class CompositeSpecification<T> : ISpecification<T>
    {
        public abstract bool IsSatisfiedBy(T candidate);


        public ISpecification<T> And(ISpecification<T> other)
        {
            return new AndSpecification<T>(this, other);
        }
    }

    internal class OneCompanySpecification : CompositeSpecification<ICollection<Company>>
    {
        public override bool IsSatisfiedBy(ICollection<Company> candidate)
        {
            return candidate.Count == 1;
        }
    }


}
